package org.basis.nio.c4;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * NIO服务端测试
 *
 * @author Mr_wenpan@163.com 2021/09/14 16:33
 */
@Slf4j
public class Server {

    public static void main(String[] args) throws IOException {
        // 1、创建selector，管理多个channel
        Selector selector = Selector.open();
        // 服务端的socketchannel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 设置这个ServerSocketChannel为非阻塞
        ssc.configureBlocking(false);

        // 2、建立selector和channel的联系（即，将channel注册到selector上）
        // SelectionKey 就是将来事件发生后，通过它可以知道事件和哪个channel的事件，第二个参数0指的是不关注任何事件（也可以指定关注的事件）
        SelectionKey sscKey = ssc.register(selector, 0, null);
        // key 只关注 accept 事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        log.info("sscKey:{}", sscKey);
        // 设置ServerSocketChannel监听8080端口
        ssc.bind(new InetSocketAddress(8080));

        while (true) {
            //  3、select 方法, 没有事件发生，线程阻塞，有事件，线程才会恢复运行
            //  【 select 在事件未处理时，它不会阻塞, 事件发生后要么处理，要么取消，不能置之不理（置之不理则会一直循环） 】
            selector.select();
            // 4、处理事件， selectedKeys 内部包含了所有发生的事件
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                // 【注意】处理key 时，要从 【selectedKeys】 集合中删除，否则下次处理就会有问题
                iter.remove();
                log.info("key : {}", key);
                // 5、区分事件类型
                // 是accept类型的事件
                if (key.isAcceptable()) {
                    // 通过key来获取这个key对应的channel
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    // 设置非阻塞
                    sc.configureBlocking(false);

                    // 将这个SocketChannel注册到selector中（以便selector能管理这个channel，一旦这个channel有事件发生，则能够被selector选择出来）
                    SelectionKey scKey = sc.register(selector, 0, null);
                    // 这个key关注read事件
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}", sc);
                    log.debug("scKey:{}", scKey);
                } else if (key.isReadable()) {
                    // 说明：客户端正常断开和异常断开，都会向server端发起一个read事件（仔细体会这里客户端正常断开和异常断开的问题）
                    try {
                        // 是read类型的事件
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(4);
                        // 读取channel中的数据到buffer中
                        int read = channel.read(buffer);
                        // 这里为什么要这么判断呢？因为在客户端正常断开的时候会触发一个read事件，这里接收到的read值为-1
                        // 当客户端正常请求断开时，这里需要将这个客户端的key从selector中移除，否则这个read事件就会一直被重复处理
                        if (read == -1) {
                            key.channel();
                        }
                        buffer.flip();
//                    debugAll(buffer);
                        System.out.println(Charset.defaultCharset().decode(buffer));
                    } catch (Exception ex) {
                        ex.printStackTrace();
                        // 当客户端异常断开时会触发一个read事件，进入这个if分支里，并且抛出异常。在抛出异常后说明客户端异常断开了
                        // 那么既然客户端断开了，那么该客户端对应的SelectionKey（或channel）也应该从selector中删除，否则selector中
                        // 一直会有这个read事件，造成selector一直循环
                        key.cancel();
                    }

                }
            }

        }

    }
}
